package org.coco24.midiutils

import javax.sound.midi.MidiEvent
import javax.sound.midi.ShortMessage
import kotlin.experimental.and

typealias MidiEvents = ArrayList<MidiEvent>

operator fun MidiEvent.get(i: Int): Byte = this.message.message[i]
fun MidiEvent.isProgramChangeEvent(): Boolean = this[0] and 0xf0.toByte() == 0xc0.toByte()
fun MidiEvent.isNoteOnEvent(): Boolean = this[0] and 0xf0.toByte() == 0x90.toByte() && this[2] > 0x00.toByte()
fun MidiEvent.isNoteOffEvent(): Boolean = this[0] and 0xf0.toByte() == 0x80.toByte() || (this[0] and 0xf0.toByte() == 0x90.toByte() && this[2] == 0x00.toByte())
fun MidiEvent.isControllerEvent(): Boolean = this[0] and 0xf0.toByte() == 0xb0.toByte()
fun MidiEvent.isPitchWheelEvent(): Boolean = this[0] and 0xf0.toByte() == 0xe0.toByte()
fun MidiEvent.isChannelEvent(): Boolean = this[0] >= 0x80.toByte() && this[0] < 0xf0.toByte()
var MidiEvent.channel: Byte
    get() = this[0] and 0x0f.toByte()
    set(channel) {
        val message = this.message
        if(message is ShortMessage) {
            message.setMessage(message.command, channel.toInt(), message.data1, message.data2)
        } else throw Exception("通道消息必须为ShortMessage")
    }

fun MidiEvent.show(): String = this.message.message.fold("", {acc, byte ->
    acc + "0x%x ".format(byte)
})
fun <T> Iterator<T>.nextOrNull(): T? = if(this.hasNext()) this.next() else null
fun <T, K> Iterable<T>.toSetBy(f: (T) -> K): Set<T> {
    val buffer = this.map(f).toHashSet()
    return this.filter {
        val value = f(it)
        if(buffer.contains(value)) {
            buffer.remove(value)
            true
        } else false
    }.toSet()
}
fun midiEventComparator(a: MidiEvent, b: MidiEvent): Int { //MIDI事件的比较器，MIDI事件Tick相同时，规定处理的优先级
    var res = (a.tick - b.tick).toInt()
    if (res == 0) {
        if (a.isNoteOffEvent() != b.isNoteOffEvent())
            res = if (a.isNoteOffEvent()) -1
            else 1
    } //NoteOff先于控制器
    if (res == 0 && a.tick != 0L && b.tick != 0L) {
        if (a.isControllerEvent() != b.isControllerEvent())
            res = if (a.isControllerEvent()) -1
            else 1
    } //控制器先于音色更换
    if (res == 0) {
        if (a.isProgramChangeEvent() != b.isProgramChangeEvent())
            res = if (a.isProgramChangeEvent()) -1
            else 1
    }
    return res
}